// Copyright 2025 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

#pragma once

#include <atomic>
#include <memory>
#include <type_traits>

#include "query/frontend/semantic/symbol_table.hpp"
#include "query/metadata.hpp"
#include "query/parameters.hpp"
#include "query/plan/profile.hpp"
#include "query/trigger.hpp"
#include "storage/v2/commit_args.hpp"
#include "utils/async_timer.hpp"
#include "utils/counter.hpp"

#include "query/frame_change.hpp"
#include "query/hops_limit.hpp"

namespace memgraph::query {

enum class TransactionStatus {
  IDLE,
  ACTIVE,
  VERIFYING,
  TERMINATED,
  STARTED_COMMITTING,
  STARTED_ROLLBACK,
};

struct Scope {
  bool in_merge{false};
};

struct EvaluationContext {
  /// Memory for allocations during evaluation of a *single* Pull call.
  ///
  /// Although the assigned memory may live longer than the duration of a Pull
  /// (e.g. memory is the same as the whole execution memory), you have to treat
  /// it as if the lifetime is only valid during the Pull.
  utils::MemoryResource *memory{utils::NewDeleteResource()};
  int64_t timestamp{-1};
  Parameters parameters{};
  /// All properties indexable via PropertyIx
  std::vector<storage::PropertyId> properties{};
  /// All labels indexable via LabelIx
  std::vector<storage::LabelId> labels{};
  /// All edgetypes indexable via EdgeTypeIx
  std::vector<storage::EdgeTypeId> edgetypes{};
  /// All counters generated by `counter` function, mutable because the function
  /// modifies the values
  mutable std::unordered_map<std::string, int64_t> counters{};
  Scope scope{};
};

std::vector<storage::PropertyId> NamesToProperties(const std::vector<std::string> &property_names, DbAccessor *dba);

std::vector<storage::LabelId> NamesToLabels(const std::vector<std::string> &label_names, DbAccessor *dba);

std::vector<storage::EdgeTypeId> NamesToEdgeTypes(const std::vector<std::string> &edgetype_names, DbAccessor *dba);

struct StoppingContext {
  // Even though this is called `StoppingContext`. TransactionStatus is used for more
  std::atomic<TransactionStatus> *transaction_status{nullptr};
  std::atomic<bool> *is_shutting_down{nullptr};
  std::shared_ptr<utils::AsyncTimer> timer{};

  auto MustAbort() const noexcept -> AbortReason {
    if (transaction_status && transaction_status->load(std::memory_order_acquire) == TransactionStatus::TERMINATED)
        [[unlikely]] {
      return AbortReason::TERMINATED;
    }
    if (is_shutting_down && is_shutting_down->load(std::memory_order_acquire)) [[unlikely]] {
      return AbortReason::SHUTDOWN;
    }
    if (timer && timer->IsExpired()) [[unlikely]] {
      return AbortReason::TIMEOUT;
    }
    return AbortReason::NO_ABORT;
  }

  auto MakeMaybeAborter(std::size_t n = 20) const noexcept {
    return [maybe_check_abort = utils::ResettableCounter{n}, ctx = *this]() -> AbortReason {
      // Not thread safe
      return maybe_check_abort() ? ctx.MustAbort() : AbortReason::NO_ABORT;
    };
  }
};

struct ExecutionContext {
  DbAccessor *db_accessor{nullptr};
  SymbolTable symbol_table;
  EvaluationContext evaluation_context;
  StoppingContext stopping_context;
  bool is_profile_query{false};
  std::chrono::duration<double> profile_execution_time;
  plan::ProfilingStats stats;
  plan::ProfilingStats *stats_root{nullptr};
  ExecutionStats execution_stats;
  TriggerContextCollector *trigger_context_collector{nullptr};
  FrameChangeCollector *frame_change_collector{nullptr};
  std::shared_ptr<QueryUserOrRole> user_or_role;
  int64_t number_of_hops{0};
  HopsLimit hops_limit;
  std::optional<uint64_t> periodic_commit_frequency;
#ifdef MG_ENTERPRISE
  std::unique_ptr<FineGrainedAuthChecker> auth_checker{nullptr};
#endif
  std::shared_ptr<storage::DatabaseProtector> protector;
  bool is_main{true};
  auto commit_args() -> storage::CommitArgs;
};

static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");
static_assert(std::is_move_constructible_v<ExecutionContext>, "ExecutionContext must be move constructible!");

inline plan::ProfilingStatsWithTotalTime GetStatsWithTotalTime(const ExecutionContext &context) {
  return plan::ProfilingStatsWithTotalTime{context.stats, context.profile_execution_time};
}

}  // namespace memgraph::query
